﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
using QAToolsAddin.Components;

namespace QAToolsAddin {

    class RandomString {

        delegate string StringGenerator();

        private bool _unique;
        StringGenerator _generate;
        Random _random = new Random();
        Dictionary<char, string> _chars = new Dictionary<char, string>();
        Dictionary<string, bool> _generated = new Dictionary<string, bool>();

        public RandomString(string pattern, bool unique) {
            _unique = unique;
            var W = new StringBuilder();
            var A = new StringBuilder();
            for (int i = 0; i < 256; i++) {
                A.Append((char)i);
                if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || (i >= '0' && i <= '9') || (i == '_')))
                    W.Append((char)i);
            }
            _chars.Add('W', W.ToString());
            _chars.Add('A', A.ToString());
            _chars.Add('w', @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_");
            _chars.Add('d', @"0123456789");
            _chars.Add('h', @"0123456789abcdef");
            _chars.Add('H', @"0123456789ABCDEF");
            _chars.Add('P', @"!""#$%&'()*+,-./:;<=>?@[\\]^_`{|}~");
            _generate = BuildStringGenerator(pattern);
        }

        public string Next() {
            var value = _generate();
            if (_unique) {
                int loop = 0;
                while (_generated.ContainsKey(value)) {
                    value = _generate();
                    if (loop++ > 10000)
                        throw new InputError("Faild to generate unique values.");
                    _generated.Add(value, false);
                }
            }
            return value;
        }

        StringGenerator BuildStringGenerator(string pattern) {
            pattern = decode_chars(pattern);
            var gens = new List<StringGenerator>();
            var re = new Regex(@"((?<![^\\]\\)\[(?:\\.|[^\]])+\]|(?<!\\)\\[AdwWhHP])(?:\{ *(\d+)(?:, *(\d+))? *\})?");
            var index = 0;
            foreach (Match m in re.Matches(pattern)) {
                if (index != m.Index) {
                    var stdchars = clean_escapes(pattern.Substring(index, m.Index - index));
                    gens.Add(() => stdchars);
                }
                index = m.Index + m.Length;
                if (m.Groups[1] != null) {
                    var chars = clean_escapes(expand_ranges(m.Groups[1].Value.TrimStart('[').TrimEnd(']')));
                    var rand_min = m.Groups[2].Success ? int.Parse(m.Groups[2].Value) : 1;
                    var rand_max = m.Groups[3].Success ? int.Parse(m.Groups[3].Value) : rand_min;
                    gens.Add(() => {
                        var sb = new StringBuilder();
                        var nb = rand_min + _random.Next(rand_max - rand_min + 1);
                        while (nb-- > 0)
                            sb.Append(chars[_random.Next(chars.Length)]);
                        return sb.ToString();
                    });
                }
            }
            if (index != pattern.Length) {
                var stdchars = clean_escapes(pattern.Substring(index));
                gens.Add(() => stdchars);
            }
            return delegate() {
                var sb = new StringBuilder();
                foreach (var gen in gens)
                    sb.Append(gen());
                return sb.ToString();
            };
        }
        
        string expand_ranges(string text) {
            return Regex.Replace(text, @"(\\.|[^\\])-(\\.|.)|(?<!\\)\\([AdwWhHP])", new MatchEvaluator(m => {
                if (m.Groups[3].Success) {
                    return _chars[m.Groups[3].Value[0]];
                } else {
                    var v1 = m.Groups[1].Value;
                    var v2 = m.Groups[2].Value;
                    return generate_range(v1[v1.Length - 1], v2[v2.Length - 1]);
                }
            }));
        }

        static string generate_range(char start, char end) {
            var txt = new StringBuilder();
            for (; start <= end; start = (char)(start + 1))
                txt.Append(start);
            return txt.ToString();
        }

        static string decode_chars(string text) {
            return Regex.Replace(text, @"\\x(\d\d)", new MatchEvaluator(m => ((char)Convert.ToInt32(m.Groups[1].Value, 16)).ToString()))
                .Replace(@"\n", "\n").Replace(@"\r", "\r").Replace(@"\t", "\t");
        }

        static string clean_escapes(string text) {
            return Regex.Replace(text, @"\\([\[\]\{\\-])", "$1");
        }

    }
}
